﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Caching;
using System.Configuration;
using MX.Core.Caching.Memory;
using MX.Core.Caching.Memcached.Configuration;
using MX.Core.Extensions;

namespace MX.Core.Caching.Memcached
{
    /// <summary>
    /// 采用memcached实现的缓存
    /// </summary>
    public class MemcachedCaching : ICaching
    {
        /// <summary>
        /// 
        /// </summary>
        public MemcachedCaching()
            : this(null)
        {

        }
        /// <summary>
        /// 通过配置名称实例对象实例化
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="useObjectPool">是否使用对象池构造MemcachedClient</param>
        public MemcachedCaching(string sectionName)
        {
            this._sectionName = sectionName;
            this._monitors = new Dictionary<string, HostFileChangeMonitor>(100, StringComparer.InvariantCultureIgnoreCase);
            Init();

        }

        private string _sectionName;
        private Dictionary<string, HostFileChangeMonitor> _monitors;
        private bool _isDisposed = false;

        private MemcachedClient _client = new MemcachedClient();


        /// <summary>
        /// 获取客户端连接
        /// </summary>
        /// <returns></returns>
        public MemcachedClient GetClient()
        {
            return _client;
        }

        /// <summary>
        /// 初始化线程池
        /// </summary>
        private void Init()
        {
            ConfigSetting config = GetConfig();
            SockIOPool pool = SockIOPool.GetInstance();
            pool.SetServers(config.Servers);
            pool.InitConnections = config.InitConnections;
            pool.MinConnections = config.MinConnections;
            pool.MaxConnections = config.MaxConnections;
            pool.SocketConnectTimeout = config.SocketConnectTimeout;
            pool.SocketTimeout = config.SocketTimeout;
            pool.MaintenanceSleep = config.MaintenanceSleep;
            pool.Failover = true;
            pool.Nagle = false;
            pool.Initialize();
        }

        /// <summary>
        /// 获取配置
        /// </summary>
        /// <returns></returns>
        private ConfigSetting GetConfig()
        {
            _sectionName = _sectionName ?? @"MX.Caching.Memcached";
            var section = (MemcachedClientSection)System.Configuration.ConfigurationManager.GetSection(_sectionName);
            if (section != null)
            {
                var result = new ConfigSetting();
                result.InitConnections = section.SocketPool.MinPoolSize;
                result.MaintenanceSleep = 30;
                result.MaxConnections = section.SocketPool.MaxPoolSize;
                result.MinConnections = section.SocketPool.MinPoolSize;
                result.SocketConnectTimeout = (int)section.SocketPool.ConnectionTimeout.TotalMilliseconds;
                result.SocketTimeout = (int)section.SocketPool.DeadTimeout.TotalMilliseconds;
                result.Servers = new string[section.Servers.Count];
                if (result.Servers.Length < 1)
                {
                    throw new NotSupportedException("memcached server undefine");
                }
                else
                {
                    var sourceServers = section.Servers.ToIPEndPointCollection();
                    for (int i = 0; i < result.Servers.Length; i++)
                    {
                        var server = sourceServers[i];
                        result.Servers[i] = server.Address + ":" + server.Port;
                    }
                }
                return result;
            }
            else
            {
                throw new NotSupportedException("memcached config happend some error");
            }
        }

        /// <summary>
        /// 获取某个项
        /// </summary>
        /// <param name="key">键值</param>
        /// <returns></returns>
        public object Get(string key)
        {
            return GetClient().Get<object>(key);
        }
        /// <summary>
        /// 获取某个项
        /// </summary>
        /// <param name="key">键值</param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            return GetClient().Get<T>(key);
        }
        /// <summary>
        /// 尝试获取某项数据
        /// </summary>
        /// <typeparam name="T">值类型</typeparam>
        /// <param name="key">键值</param>
        /// <param name="value">返回的值，如果不存在则返回默认值</param>
        /// <returns>是否获取成功</returns>
        public bool TryGet<T>(string key, out T value)
        {
            object t;
            try
            {
                value = GetClient().Get<T>(key);
                return Common.CheckValueNull(value);
            }
            catch { }
            value = default(T);
            return false;
        }
        /// <summary>
        /// 获取一批数据
        /// </summary>
        /// <param name="keys">键值</param>
        /// <returns></returns>
        public IDictionary<string, object> GetAll(string[] keys)
        {
            var hTable = GetClient().GetMultiple<object>(keys);
            var dic = new Dictionary<string, object>();
            foreach (var key in keys)
            {
                dic.Add(key, hTable[key]);
            }
            return dic;
        }

        /// <summary>
        /// 获取一批数据
        /// </summary>
        /// <param name="keys">键值</param>
        /// <returns></returns>
        public IDictionary<string, T> GetAll<T>(string[] keys)
        {
            var hTable = GetClient().GetMultiple<T>(keys);
            Dictionary<string, T> dic = new Dictionary<string, T>();
            foreach (var key in keys)
            {
                dic.Add(key, (T)hTable[key]);
            }
            return dic;
        }

        /// <summary>
        /// 删除某个数据
        /// </summary>
        /// <param name="key">键值</param>
        /// <returns></returns>
        public bool Remove(string key)
        {
            return GetClient().Delete(key);
        }

        /// <summary>
        /// 删除一批数据
        /// </summary>
        /// <param name="keys">键值</param>
        /// <returns></returns>
        public void RemoveAll(IEnumerable<string> keys)
        {
            foreach (var key in keys) GetClient().Delete(key);
        }

        /// <summary>
        /// 判断是否存在某个键值的缓存数据
        /// </summary>
        /// <param name="key">键值</param>
        /// <returns></returns>
        public bool Exists(string key)
        {
            return GetClient().KeyExists(key);
        }

        /// <summary>
        /// 设置一个缓存项
        /// </summary>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        /// <returns></returns>
        public void Set(string key, object data)
        {
            Set<object>(key, data);
        }
        /// <summary>
        /// 设置一个明确类型的缓存项
        /// </summary>
        /// <typeparam name="T">缓存数据的类型</typeparam>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        public void Set<T>(string key, T data)
        {
            Set(key, data, DateTime.MaxValue);
        }
        /// <summary>
        /// 设置一个缓存项并且设置绝对过期时间
        /// </summary>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        /// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
        /// <returns></returns>
        public void Set(string key, object data, DateTimeOffset absoluteExpiration)
        {
            Set<object>(key, data, absoluteExpiration.DateTime);
        }
        /// <summary>
        /// 设置一个明确类型的缓存项并且设置绝对过期时间
        /// </summary>
        /// <typeparam name="T">缓存数据的类型</typeparam>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        /// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
        public void Set<T>(string key, T data, DateTimeOffset absoluteExpiration)
        {
            int count = 0;
            while (count++ < 10)
                if (GetClient().Set(key, data, absoluteExpiration.DateTime)) break;

        }
        /// <summary>
        /// 设置一个与某些文件关连的缓存项,当这些文件被修改时缓存过期
        /// </summary>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        /// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
        /// <returns></returns>
        public void Set(string key, object data, params string[] dependentFiles)
        {
            if (this.RegisterMoniter(key, dependentFiles))
            {
                this.Set(key, data);
            }
        }
        /// <summary>
        /// 设置一个明确类型的且与某些文件关连的缓存项,当这些文件被修改时缓存过期
        /// </summary>
        /// <typeparam name="T">缓存数据的类型</typeparam>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        /// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
        /// <returns></returns>
        public void Set<T>(string key, T data, params string[] dependentFiles)
        {
            if (this.RegisterMoniter(key, dependentFiles))
            {
                this.Set<T>(key, data);
            }
        }
        /// <summary>
        /// 设置一个具有过期时间并且与某些文件关连的缓存项,当过期时间已到或这些文件被修改时缓存过期
        /// </summary>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        /// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
        /// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
        /// <returns></returns>
        public void Set(string key, object data, DateTimeOffset absoluteExpiration, params string[] dependentFiles)
        {
            if (this.RegisterMoniter(key, dependentFiles))
            {
                this.Set(key, data, absoluteExpiration);
            }
        }

        /// <summary>
        /// 设置一个明确类型的且具有过期时间并且与某些文件关连的缓存项,当过期时间已到或这些文件被修改时缓存过期
        /// </summary>
        /// <typeparam name="T">缓存数据的类型</typeparam>
        /// <param name="key">键值</param>
        /// <param name="data">缓存数据</param>
        /// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
        /// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
        /// <returns></returns>
        public void Set<T>(string key, T data, DateTimeOffset absoluteExpiration, params string[] dependentFiles)
        {
            if (this.RegisterMoniter(key, dependentFiles))
            {
                this.Set<T>(key, data, absoluteExpiration);
            }
        }

        #region GetOrAdd
        /// <summary>
        /// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存项键值</param>
        /// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
        /// <returns></returns>
        public T GetOrAdd<T>(string key, Func<T> handler)
        {
            T value;
            if (TryGet<T>(key, out value))
            {
                return value;
            }
            else
            {
                value = handler.Invoke();
                if (Common.CheckValueNull(value))
                {
                    //存入缓存
                    Set<T>(key, value);
                }
                return value;
            };
        }
        /// <summary>
        /// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存项键值</param>
        /// <param name="absoluteExpiration">绝对的过期时间，当时间超过此值时缓存项将自动失效</param>
        /// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
        /// <returns></returns>
        public T GetOrAdd<T>(string key, Func<T> handler, DateTimeOffset absoluteExpiration)
        {
            T value;
            if (TryGet<T>(key, out value))
            {
                return value;
            }
            else
            {
                value = handler.Invoke();
                if (Common.CheckValueNull(value))
                {
                    //存入缓存
                    Set<T>(key, value, absoluteExpiration);
                }
                return value;
            };
        }

        /// <summary>
        /// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存项键值</param>
        /// <param name="files">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
        /// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
        /// <returns></returns>
        public T GetOrAdd<T>(string key, Func<T> handler, params string[] files)
        {
            if (this.RegisterMoniter(key, files))
            {
                T value;
                if (TryGet<T>(key, out value))
                {
                    return value;
                }
                else
                {
                    value = handler.Invoke();
                    if (Common.CheckValueNull(value))
                    {
                        //存入缓存
                        Set<T>(key, value);
                    }
                    return value;
                };
            }
            else
            {
                return default(T);
            }
        }
        /// <summary>
        /// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存项键值</param>
        /// <param name="absoluteExpiration">绝对的过期时间，当时间超过此值时缓存项将自动失效</param>
        /// <param name="files">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
        /// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
        /// <returns></returns>
        public T GetOrAdd<T>(string key, Func<T> handler, DateTimeOffset absoluteExpiration, params string[] files)
        {
            if (this.RegisterMoniter(key, files))
            {
                T value;
                if (TryGet<T>(key, out value))
                {
                    return value;
                }
                else
                {
                    value = handler.Invoke();
                    if (Common.CheckValueNull(value))
                    {
                        //存入缓存
                        Set<T>(key, value, absoluteExpiration);
                    }
                    return value;
                };
            }
            else
            {
                return default(T);
            }
        }
        #endregion

        /// <summary>
        /// 增加值
        /// </summary>
        /// <param name="key">缓存项键值</param>
        /// <param name="amount">需要增加的值</param>
        /// <returns></returns>
        public long Increment(string key, uint amount)
        {
            return (long)GetClient().Increment(key, 0, (int)amount);
        }

        /// <summary>
        /// 减去值
        /// </summary>
        /// <param name="key">缓存项键值</param>
        /// <param name="amount">需要减去的值</param>
        /// <returns></returns>
        public long Decrement(string key, uint amount)
        {
            return (long)GetClient().Decrement(key, 0, (int)amount);
        }

        /// <summary>
        /// 注册监控器
        /// </summary>
        /// <param name="key"></param>
        /// <param name="dependentFiles"></param>
        private bool RegisterMoniter(string key, params string[] dependentFiles)
        {
            if (!_isDisposed)
            {
                string moniterKey = string.Concat(key, "|", string.Join("|", dependentFiles));
                if (!this._monitors.ContainsKey(moniterKey))
                {
                    lock (this._monitors)
                    {
                        if (!this._monitors.ContainsKey(moniterKey))
                        {
                            var moniter = new HostFileChangeMonitor(dependentFiles);
                            moniter.NotifyOnChanged(new OnChangedCallback(state =>
                            {
                                try
                                {
                                    Remove(key);
                                    moniter.Dispose();
                                }
                                catch { }
                                finally
                                {
                                    //重新注册
                                    this._monitors.Remove(moniterKey);
                                    RegisterMoniter(key, dependentFiles);
                                }
                            }));
                            this._monitors.Add(moniterKey, moniter);
                        }
                    }
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            if (!this._isDisposed)
            {
                foreach (var m in this._monitors)
                {
                    m.Value.Dispose();
                }
                this._monitors.Clear();
                if (_client != null)
                {
                    _client = null;
                }
                this._isDisposed = true;
            }
        }

        #region 静态实例
        private static object _initLock = new object();
        /// <summary>
        /// 缓存实例
        /// </summary>
        private static ICaching _cacheInstance = null;
        /// <summary>
        /// 获取默认的缓存实例
        /// </summary>
        public static ICaching Default
        {
            get
            {
                if (_cacheInstance == null)
                {
                    lock (_initLock)
                    {
                        if (_cacheInstance == null)
                        {
                            string sectionName = ConfigurationManager.AppSettings["MX.Cache.Memcached.SectionName"];
                            _cacheInstance = new MemcachedCaching(sectionName);
                        }
                    }
                }
                return _cacheInstance;
            }
        }
        #endregion
    }
}
